
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       sim_uart.c
  * @author     baiyang
  * @date       2022-12-3
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "sim_uart.h"

#include <stdio.h>
#include <stdlib.h>
//#include <string.h>

#include <posix/string.h>

#include <rthw.h>
#include <rtthread.h>
/*-----------------------------------macro------------------------------------*/
#define SIM_UART_TASK_THREAD_PRIORITY     25
#define SIM_UART_TASK_THREAD_STACK 2048

#ifndef MIN
#define MIN(a, b)         ((a) < (b) ? (a) : (b))
#endif
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/
static bool _select_check(int fd);
/*----------------------------------variable----------------------------------*/

/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
/**
  * @brief       
  * @param[in]   dev  
  * @param[out]  
  * @retval      
  * @note        
  */
static rt_err_t _init(rt_device_t dev)
{
    return RT_EOK;
}


static rt_err_t _open(rt_device_t dev, rt_uint16_t oflag)
{
    return RT_EOK;
}


static rt_err_t _close(rt_device_t dev)
{
    return RT_EOK;
}

static rt_size_t _read(struct rt_device* dev, rt_off_t pos, void* buffer, rt_size_t size)
{
    rt_size_t ret = 0;

    sim_uart_t sim_uart = (sim_uart_t)dev;

    if (!sim_uart->_connected) {
        return 0;
    }

    /* disable interrupt */
    rt_base_t level = rt_hw_interrupt_disable();

    ret = rt_ringbuffer_get(&sim_uart->rxbuf, (uint8_t *)buffer, (uint16_t)size);

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    return ret;
}

static rt_size_t _write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    rt_size_t ret = 0;
    ssize_t send_size = 0;

    sim_uart_t sim_uart = (sim_uart_t)dev;

    if (!sim_uart->_connected) {
        return 0;
    }

    if (sim_uart->_use_socket_connected) {
        send_size = send(sim_uart->fd, buffer, size, 0);
    } else {
        send_size= sendto(sim_uart->fd, buffer, size, 0, (struct sockaddr *)&sim_uart->sockaddr_i, sizeof(sim_uart->sockaddr_i));
    }

    if (send_size > -1) {
        ret = (rt_size_t)send_size;
    }

    if (dev->tx_complete && ret == size) {
        dev->tx_complete(dev, (void *)buffer);
    }

    return ret;
}

static rt_err_t _control(struct rt_device* dev, int cmd, void* args)
{
    return RT_EOK;
}

/*
  use select() to see if something is pending
 */
static bool _select_check(int fd)
{
    if (fd == -1) {
        return false;
    }

    fd_set fds;
    struct timeval tv;

    FD_ZERO(&fds);
    FD_SET(fd, &fds);

    // zero time means immediate return from select()
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    if (select(fd+1, &fds, NULL, NULL, &tv) == 1) {
        return true;
    }

    return false;
}

/*
  start a UDP client connection for the serial port.
 */
static void _udp_start_client(sim_uart_t uart, const char *address, uint16_t port)
{
    int ret = false;

    if (uart->_connected) {
        return;
    }

    uart->_is_udp = true;

    memset(&uart->sockaddr_i,0,sizeof(struct sockaddr_in));

    uart->sockaddr_i.sin_len = sizeof(struct sockaddr_in);
    uart->sockaddr_i.sin_port = htons(port);
    uart->sockaddr_i.sin_family = AF_INET;
    uart->sockaddr_i.sin_addr.s_addr = inet_addr(address);

    uart->fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (uart->fd == -1) {
        printf("[sim_uart]: socket failed\n");
        return;
    }

    // try to setup for broadcast, this may fail if insufficient privileges
    int one = 1;
    setsockopt(uart->fd,SOL_SOCKET,SO_BROADCAST,(char *)&one,sizeof(one));

    struct timeval tv;
    tv.tv_sec = 1000 / 1000;
    tv.tv_usec = (1000 % 1000) * 1000;

    setsockopt(uart->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    ret = connect(uart->fd, (struct sockaddr *)&uart->sockaddr_i, sizeof(uart->sockaddr_i));
    if (ret == -1) {
        printf("[sim_uart]: udp connect failed on port %u\n", port);
        return;
    }

    uart->_connected = true;
    uart->_use_socket_connected = true;
}

static int _uart_register(rt_device_t simuart, const char* dev_name)
{
    rt_uint16_t flag = RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_TX \
                       | RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_DMA_RX \
                       | RT_DEVICE_FLAG_INT_RX;

    struct rt_device* device = simuart;

    RT_ASSERT(device != RT_NULL);

    device->type        = RT_Device_Class_Char; //是否修改为这个？
    device->ref_count   = 0;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

    device->init        = _init;
    device->open        = _open;
    device->close       = _close;
    device->read        = _read;
    device->write       = _write;
    device->control     = _control;

    return rt_device_register(device, dev_name, flag);
}

/*
  per-bus callback thread
*/
static void _rx_thread(void *arg)
{
    while (true) {
        ssize_t recv_size = -1;

        sim_uart_t uart = (sim_uart_t)arg;
        char rx[128] = {0};

        if (!uart->_connected) {
            continue;
        }

        if (uart->_use_socket_connected) {
            recv_size = recv(uart->fd, rx, sizeof(rx), 0);
        } else {
            struct sockaddr_in from = { 0 };
            socklen_t from_len = sizeof(from);
            recv_size = recvfrom(uart->fd, rx, sizeof(rx), 0, (struct sockaddr*)&from, &from_len);
        }

        if (recv_size > -1 && uart->_parent.ref_count > 0) {
            /* disable interrupt */
            rt_base_t level = rt_hw_interrupt_disable();

            rt_ringbuffer_put_force(&uart->rxbuf, rx, (uint16_t)recv_size);

            /* enable interrupt */
            rt_hw_interrupt_enable(level);
        }

        rt_thread_mdelay(2);
    }
}

int  sim_uart_init(sim_uart_t uart, const char* dev_name, const char *path, uint16_t tcp_base_port, uint16_t udp_port_offset)
{
    memset(uart, 0, sizeof(struct sim_uart));

    rt_ringbuffer_init(&uart->rxbuf, uart->rxbuf_pool, sizeof(uart->rxbuf_pool));

    char *saveptr = NULL;
    char *s = strdup(path);
    char *devtype = strtok_r(s, ":", &saveptr);
    char *args1 = strtok_r(NULL, ":", &saveptr);
    char *args2 = strtok_r(NULL, ":", &saveptr);

    if (strcmp(devtype, "tcp") == 0) {
        uint16_t port = atoi(args1);
        bool wait = (args2 && strcmp(args2, "wait") == 0);
        //_tcp_start_connection(port, wait);
    } else if (strcmp(devtype, "udpclient") == 0) {
        // udp client connection
        const char *ip = args1;
        uint16_t port = args2?atoi(args2):14550;
        port += udp_port_offset;

        if (!uart->_connected) {
            printf("UDP connection %s:%u\n", ip, port);
            _udp_start_client(uart, ip, port);
        }
    }

    free(s);

    uart->thread_ctx = rt_thread_create(dev_name,_rx_thread, (void *)uart, SIM_UART_TASK_THREAD_STACK, SIM_UART_TASK_THREAD_PRIORITY, 2);
    if (uart->thread_ctx != NULL) {
        rt_thread_startup(uart->thread_ctx);
    }

    return _uart_register((rt_device_t)uart, dev_name);
}
/*------------------------------------test------------------------------------*/


